/**
 * 
 */
package org.openzip.jarsfx;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author Lex Viatkine
 */
public class Archiver implements IArchiver
{
	static final String MANIFEST_DIR = "META-INF/";
	static final String MANIFEST = JarFile.MANIFEST_NAME;
	static final String VERSION = "1.0";
	private List<File> _rootFolders = null;

	private IRefreshablePacker _refreshablePacker = null;

	public void setRefreshablePacker(IRefreshablePacker packer){
		this._refreshablePacker = packer;
	}

	@Override public void makeArchive(File path, Collection<File> files)
				throws IOException{
		this._rootFolders = new ArrayList<File>();
		this._calcRoots(files);

		JarOutputStream output = new JarOutputStream(new FileOutputStream(path));

		output.setMethod(ZipOutputStream.DEFLATED);
		output.setLevel(8);

		// vytvorime manifest

		// Construct a string version of a manifest
		StringBuffer sbuf = new StringBuffer();
		sbuf.append("Manifest-Version: 1.0\n");
		sbuf.append("Main-Class: org.openzip.jarsfx."
					+ "unpacking.controller.UnpackerController\n");

		// Convert the string to a input stream
		InputStream is =
					new ByteArrayInputStream(sbuf.toString().getBytes("UTF-8"));

		Manifest manifest = new Manifest(is);

		// Attributes global = manifest.getMainAttributes();
		// global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
		//
		// Attributes main = manifest.getAttributes("Main-Class");
		// main.put("Main-Class",
		// "org.openzip.openzip.unpacking.controller.UnpackerController");

		// zapisem manifest
		ZipEntry e = new ZipEntry(MANIFEST_DIR);
		e.setTime(System.currentTimeMillis());
		e.setSize(0);
		e.setCrc(0);
		output.putNextEntry(e);
		e = new ZipEntry(MANIFEST);
		e.setTime(System.currentTimeMillis());
		output.putNextEntry(e);
		manifest.write(output);
		output.closeEntry();

		// pribalime robalovaci soubory
		this._addExtractingClasses(output);

		// vypocteme celkovou velikost a hodime do packeru
		long totalLenght = 0;
		for(File file : files){
			totalLenght = totalLenght + file.length();
		}
		if(this._refreshablePacker != null){
			this._refreshablePacker.setTotalSize(totalLenght);
		}

		// a ted tam mrsknem vsechny soubory do /data/
		for(File file : files){
			this._addFile(output, file);
		}

		output.close();

		// oznamime refreshablovi ze je konec
		if(this._refreshablePacker != null){
			this._refreshablePacker.taskCompleted();
		}
	}

	private void _addExtractingClasses(ZipOutputStream out) throws IOException{
		byte buf[] = new byte[1024];

		// tohle nacte jar file ve kterem se potom da listovat
		// protoze to ale eclipse nespousti z jaru pouziju kolekci

		String jarFileName =
					Archiver.class.getProtectionDomain().getCodeSource()
								.getLocation().getFile();
		JarFile jarFile = new JarFile(jarFileName);

		// String binFolder = Archiver.class.getProtectionDomain()
		// .getCodeSource().getLocation().getFile();

		// System.out.println("Bin folder: " + binFolder);

		// JarFile jarFile = new JarFile(binFolder);
		// File bin = new File(binFolder);

		// bin.listFiles();

		Enumeration entries = jarFile.entries();

		while (entries.hasMoreElements()){
			// New archive entry(file/directory inside JAR)
			JarEntry jarEntry = (JarEntry)entries.nextElement();
			System.out.println("JAR Entry: " + jarEntry.getName());

			// We want to filter-out manifest file since it's being
			// generated earlier in the code
			if(jarEntry.getName().equalsIgnoreCase("META-INF/MANIFEST.MF")){
				continue;
			}
			// jarEntry.setMethod(ZipEntry.STORED);
			JarEntry newEntry = new JarEntry(jarEntry.getName());
			out.putNextEntry(newEntry);

			InputStream in =
						Archiver.class.getResourceAsStream("/" + jarEntry.getName());

			// Transfer bytes from the file to the ZIP file
			int len;
			while ((len = in.read(buf)) > 0){
				out.write(buf, 0, len);
			}

			// Complete the entry
			out.closeEntry();
			in.close();
		}
	}

	/*
	 * Adds a new file entry to the output stream.
	 */
	private void _addFile(ZipOutputStream zos, File file) throws IOException{

		// prevedeme to na cestu kterou chceme zapsat.
		String name = this._calcEntryName(file);

		// hodime do packeru velikost aktrualniho souboru
		if(this._refreshablePacker != null){
			this._refreshablePacker.setFile(name, file.length());
		}

		long size = file.isDirectory() ? 0 : file.length();

		JarEntry e = new JarEntry(name);
		e.setTime(file.lastModified());
		if(size == 0){
			e.setMethod(ZipEntry.STORED);
			e.setSize(0);
			e.setCrc(0);
		}

		zos.putNextEntry(e);

		if(file.isFile()){
			byte[] buf = new byte[1024];
			int len;
			int progressCounter = 0;
			InputStream is = new BufferedInputStream(new FileInputStream(file));
			while ((len = is.read(buf, 0, buf.length)) != -1){
				zos.write(buf, 0, len);
				progressCounter += len;

				// tady jeste udelam aktualni progress..
				if(this._refreshablePacker != null){
					this._refreshablePacker.setFileCompletedValue(progressCounter);
				}
			}
			is.close();
		}
		zos.closeEntry();
	}

	private String _calcEntryName(File file){
		String name = file.getPath();

		// System.out.println(name);
		// System.out.println(file.getAbsolutePath());

		if(file.isDirectory()){
			// prida na konec cesty slozky "/"
			name = name.endsWith(File.separator) ? name : (name + File.separator);
		}

		System.out.println(name);
		boolean haveRoot = false;
		for(File rootFolder : this._rootFolders){
			if(name.startsWith(rootFolder.getParentFile().getPath())){
				name =
							name.substring(rootFolder.getParentFile().getPath()
										.length());
				haveRoot = true;
				break;
			}
		}

		if(!haveRoot){
			name = file.getName();
		}
		name = "data/" + name.replaceAll("\\\\", "/");

		System.out.println(">> " + name);
		return name;
	}

	private void _calcRoots(Collection<File> files){
		for(File file : files){
			if(!file.isDirectory()){
				continue;
			} // -->

			// aby jsme mohli zkontrolovat cesta slozky musi koncit na "/"
			String name = file.getPath();
			name = name.endsWith(File.separator) ? name : (name + File.separator);

			System.out.println("Delam slozku: " + name);
			boolean isSubfolder = false;

			// ted projdem jiz to co povazujeme za root a zkontrolujem zda to
			// nejsou subslozky te nasi. Pokud ano vyhodime je a dame tam nasi.
			for(Iterator<File> i = this._rootFolders.iterator(); i.hasNext();){
				// aby jsme mohli zkontrolovat cesta slozky musi koncit na "/"
				String path = i.next().getPath();
				path =
							path.endsWith(File.separator) ? path
										: (path + File.separator);

				System.out.println("- kontroluju s: " + path);
				if(path.startsWith(name)){
					i.remove();
					System.out.println(">> je nadrazena. Puvodni odsranena!");
					continue;
				}

				if(name.startsWith(path)){
					isSubfolder = true;
					System.out.println(">> je subfolder.");
				}
			}
			if(!isSubfolder){
				this._rootFolders.add(file);
				System.out.println("++ ok, pridavam");
			}
		}

		System.out.println("=====================");
		for(File folder : this._rootFolders){
			System.out.println(folder.getPath());
		}
		System.out.println("=====================");
	}

}
